Misc notes on sninit design
~~~~~~~~~~~~~~~~~~~~~~~~~~~
This file lists some design considerations that were put into sninit,
and some choices made when there were several alternatives.

It is not intended for general users, but it may be helpful
for those trying to extend sninit, or perhaps just to read the source.


telinit communication
~~~~~~~~~~~~~~~~~~~~~
Originally telinit used /dev/initctl pipe, just like sysvinit.
The pipe was subsequently replaced with a stream-oriented unix
domain socket (UDS) from the abstract namespace (see unix(7)).

UDS provide two-way communication, allowing error messages and
status reports to be passed back to telinit. Without this, it
would be "telinit q and go check syslog for results".
An alternative was using two pipes, an extremely ungraceful solution.

Stream UDS are connected, and thus automatically solve possible races
with two telinits calling sninit at the same time.

Abstract namespaces impose no dependencies on /dev filesystem.
Which happens to be really handy when it's unclear whether you'll have
a read-only space there, or a kernel-mounted ramfs with no static files.


Linux-specific features
~~~~~~~~~~~~~~~~~~~~~~~
reboot(2), getdents(2), abstract namespace for UDS etc. are not portable.

If one needs sninit for BSD, one can take it and make a BSD-specific version;
a lesser evil than having code cluttered with #ifdef's in what ultimately
is a very system-specific utility.

(later update: reboot(2), getdents(2) and abstract namespaces are apparently
supported in some BSD flavors)


The lack of console initialization in setup()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
sninit uses whatever it has got on fds 0, 1, 2.
sysvinit does set some tty parameters, however, I see no good
explanation for why this is necessary.
My opinion is that the kernel knows better.

Initial console can very well be serial, the kernel probably set it up,
no need to touch anything there.

Daemons and startup scripts should be told to use /var/log or syslog
instead of stderr (if not, let them use whatever the kernel
provided to sninit). getty does console init on its own.


systemd-style dependencies
~~~~~~~~~~~~~~~~~~~~~~~~~~
Abandoned completely. See dependencies.txt.


Services are s-type only
~~~~~~~~~~~~~~~~~~~~~~~~
Entries read from service files are assumed to be s-type only.
This is because directory contents is inherently unordered, and
I can't think of any good scenario involving non-s-type service
files anyway.

Service files are needed to simplify daemon installation.
I.e. a package puts its file to /etc/rc and that's it.
Installing something related to system initialization this
way looks like a really bad idea to me.
Also, since directories are inherently unordered, w-type entries
wouldn't make much sense anyway.

Possible application of non-s-type service files:
a file containing loadkeys is an r-type service file
owned by kbd package.


Service shell scripts
~~~~~~~~~~~~~~~~~~~~~
That is, special handling for service file starting with #!.
It was easy to implement, it didn't take much code space,
and it provides a usable way for chaining w-type actions before
a daemon s-entry. Typical problem like this:

	test -d /var/cache/squid && mkdir -p /var/cache/squid
	exec squid

would require really ugly workaround without script support.

An alternative was to pipe the file contents to the interpreter, i.e.

	#:345:/bin/sh -
	test -d /var/cache/squid && mkdir -p /var/cache/squid
	exec squid
	
This partially duplicates #! functionality, and requires sninit
to store the whole file within initrec.

However the point is that it's not the only alternative.
If one needs, one can use proper shell scripts.


Command parsing vs /bin/sh -c
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Both sysvinit and bbinit only split commands on spaces by themselves.
If anything else is needed, they call sh to parse the command.

For a long time, sninit could do much more parsing on its own,
and required an explicit ! prefix to spawn /bin/sh.

This idea has been abandoned. Complex commands are rare,
and most of them need output redirection or variable substitution,
not proper quotes handling.


Auto-inserting exec for /bin/sh -c entries
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
BB init runs /bin/sh -c "exec $command" instead of just -c "$command".
This is not done in sninit. Reasons:

	* doing so with arbitrary command is wrong

	* using some heuristic to tell whether it's ok or not
	  is unreliable and requires bypass for cases of misdetection
	  ("prefix with ! to prevent exec")

	* there is already the opposite bypass: put "exec" where
	  you need it to force exec

	* the optimization is superficial for run-once entries,
	  and most of sh-entries are going to be run-once.

	* even better optimization: write commands so that
	  they would not be shell-escaped

	* without exec-injections, executable and non-executable
	  initscripts share the same syntax. Especially if multiline.
	  With injection, it's suddenly different.

In other words, it does not look like a good idea. At all.
Shell escape itself is a kind of fallback, it's easy to implement on top
of initdir code, but it's not something that should be used extensively.


Builtins
~~~~~~~~
That is, bundling some commands with init, busybox-style. Or perhaps sh style.
Actually, there are two points here:
	1. telinit as a link to init, and
	2. "123 run -u nobody daemon" with "run" being a part of init
Both were abandoned, for now at least.

#1, telinit is much more independent from init than it is in sysvinit, due to
lack of state passing. Other than that, there are no clear reasons to bundle
them, so it was decided to keep them separate.

#2, the list of builtins could have included telinit (or "fast telinit",
without the need to exec anything), but the main reason why this may be needed
is implementing euid, egid and ulimit setting. This stuff must be done in the
child process, and e[ug]id requires reading additional passwd and group, which
is not something init needs.

This was abandoned completely in favor of companion utility, see limits.txt.


Current runlevel in argv[0]
~~~~~~~~~~~~~~~~~~~~~~~~~~~
That is, making ps output look like "init 3" instead of "/sbin/init".
Sysvinit has this feature. It's easy to implement, too.

Still I can't find any reason why this may be useful. Especially in sysvinit.

For those extremely rare occasions when it's needed, telinit ? provides
the answer.


Variable argument lists handling
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
warn() uses printf-style syntax, and I like the way it looks.
However, common C implementation (stdarg.h) produces kinda ugly
code on modern processors.
This is because va_* stuff was apparently designed with stack in mind,
while modern cpus (x86_64, arm, mips) use registers instead
(and lack indirect register addressing, not that it matters much).

To make matters worse, there's warn(fmt, ...) which calls
snprintf(buf, len, fmt, ...), note the move from 1st to 3rd position for fmt
and subsequent arguments.

Standard C is not expressive enough to declare warn() and/or vsnprintf
with explicit argument placement, at least not the way I would accept.
And doing so would introduce shaky assumptions for stack-based platforms,
specifically on sizeof(long) <=> sizeof(void*) and their alignment.

So for now the whole thing uses standard va_list approach.
Which, just in case, means saving registers to the stack in warn()
and using va_arg on those saved values.

The only optimization used is disabling FPU, which is not needed in sninit
but which typically has some registers to save and iterate over.

Quick check reveals the losses are negligible, below 100 bytes for most
architectures, so why bother.


Environment variables in inittab
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Current implementation allows setting environment variables in inittab.
With envp being completely independent from initrecs, it would make a lot
of sense to use a separate file for the environment, say, /etc/initenv.

The problem is, there are typically only one or two variables set in inittab
(PATH and... well that's about it). Too few to bother. And atop of that,
handling a second file would slightly increase the size of init,
compared to current guess-type-of-line code.

The move to crontab-like format largely alleviated the issue.
Assignments look a lot less out of place now.


SIGKILL with/out SIGTERM
~~~~~~~~~~~~~~~~~~~~~~~~
The normal way to kill a process is to send SIGTERM first, asking it to exit
gracefully, maybe do some cleanup and such. And only if the process takes
too long to die, init sends it SIGKILL.

However, turns out busybox getty just ignores SIGTERM somehow, which means
stopping it takes about TIME_TO_SIGKILL seconds.
To handle this, C_KILL was introduces as a short-cut that skips the SIGTERM
part. Since the process cannot do any cleanup, why bother asking it.

The whole thing does not look right, but I can't think of any better solution.
Why does getty ignores SIGTERM? What if there's a child process that *does*
handle SIGTERM properly?

Initially, SIGABRT was used instead of SIGTERM to achieve the same goal.
This was deemed pointless however. If we know SIGABRT will just kill it,
we can just as well send SIGKILL.
